The purpose of this lab was to transition the robot from manual control to open-loop control. By the end of the lab, the car should be able to execute a pre-programmed sequence of movements using the Artemis microcontroller and two dual motor drivers.
For the prelab section, I planned out the wiring between the Artemis, two DRV8833 motor drivers, and the batteries. Each motor driver uses both channels in parallel to drive one motor, which allows higher current delivery and more reliable startup.
The figure above is adapted from Stephan W. (2024). I used Artemis pins A0, A1, A3, A4 for control and base on the datasheet provided, they all support PWM and are close to each other.
The pictures above shows connections of the pins, I soldered the H-bridge for both motor driver and PWM pins on the Artemis board. The motor drivers are mounted close to the motors to shorten high-current paths and reduce electrical noise. The Artemis and motors are powered by separate batteries(850 mAh) so that motor current spikes and voltage drops do not disrupt the microcontroller. Finally, the wiring was routed to minimize interference and clutter, with motor wires kept short and flexible stranded wire used for durability.
I first took the car apart and cut out the factory PCB, below is an after picture with no hardware installed.
The lab manual recommends using a bench power supply during initial testing because it provides a stable voltage and adjustable current limit, which makes debugging safer and easier. But my robot was already wired with a battery connector before I saw the instruction. So based on the lab instructions and other students’ setups, a reasonable configuration is to set the supply to approximately 3.7 V to match the nominal voltage of the 850 mAh Li-ion battery used later in the lab, with a current limit around 1–1.5 A. This current limit is high enough to allow the motors to start and run normally while still protecting the motor driver and wiring in case of a short or wiring mistake. Using a regulated supply also avoids voltage drops associated with partially discharged batteries and allows consistent testing conditions.
To verify that PWM was regulating the power delivered to the motor driver, I used analogWrite() to drive the motor driver input while observing the output with an oscilloscope. The probe tip was connected to the motor driver output node and the ground clip was connected to system ground. The following code was used to generate a constant PWM signal.
#define IN1 3
void setup() {
pinMode(IN1, OUTPUT);
}
void loop() {
analogWrite(IN1, 50);
delay(2000);
}
The oscilloscope waveform picture shows a repeating switching pattern between low and higher voltage levels, The pulse width corresponds to the duty cycle set by the analogWrite() command. But Because the robot had already been fully assembled and soldered before testing, the waveform contains noticeable electrical noise and voltage spikes from the environment and exposed wires. Below is the video showing the regulation.
First, To verify that the motor driver and wiring were functioning correctly, the robot was placed on its side so the wheels could spin freely forward and backward. The motor driver was powered using an external power supply as we discussed earlier, and all grounds were connected.
#define MOTOR_A_FWD 3
#define MOTOR_A_BWD 4
int speedVal = 50;
void setup() {
pinMode(MOTOR_A_FWD, OUTPUT);
pinMode(MOTOR_A_BWD, OUTPUT);
}
void loop() {
// FORWARD
analogWrite(MOTOR_A_FWD, speedVal);
analogWrite(MOTOR_A_BWD, 0);
delay(3000);
// REVERSE
analogWrite(MOTOR_A_FWD, 0);
analogWrite(MOTOR_A_BWD, speedVal);
delay(3000);
}
After verifying a single motor, both motor drivers were connected and tested together to confirm that the robot could drive both wheels in the same direction and reverse them simultaneously. The following code was used to run both motors forward and backward:
// Motor A (left)
#define MOTOR_A_FWD 3
#define MOTOR_A_BWD 4
// Motor B (right)
#define MOTOR_B_FWD 1
#define MOTOR_B_BWD 0
int speedVal = 80;
void setup() {
pinMode(MOTOR_A_FWD, OUTPUT);
pinMode(MOTOR_A_BWD, OUTPUT);
pinMode(MOTOR_B_FWD, OUTPUT);
pinMode(MOTOR_B_BWD, OUTPUT);
}
void loop() {
// FORWARD
analogWrite(MOTOR_A_FWD, speedVal);
analogWrite(MOTOR_A_BWD, 0);
analogWrite(MOTOR_B_FWD, speedVal);
analogWrite(MOTOR_B_BWD, 0);
delay(3000);
// REVERSE
analogWrite(MOTOR_A_FWD, 0);
analogWrite(MOTOR_A_BWD, speedVal);
analogWrite(MOTOR_B_FWD, 0);
analogWrite(MOTOR_B_BWD, speedVal);
delay(3000);
}
When running both motors together, the video also shows that the two wheels do not spin at the exact same rate. This is expected due to small differences in motors, gearboxes, and friction, and it causes the car to drift when driving on the ground. In a later section, I show how I addressed this by adding a calibration factor to better match the two motor speeds.
The picture above shows the fully assembled robot with all components secured inside the chassis. The Artemis board, dual motor drivers, motor driver battery, ToF sensors, and IMU are mounted firmly to prevent movement during operation. The Artemis battery is placed at the bottom of the original chassis battery room for better storage. Wires are routed compactly to reduce clutter and avoid interference with moving parts.
To control the car in open-loop, I programmed the Artemis to receive BLE commands from my laptop. On the Arduino side, the firmware listens for a DRIVE command that contains two signed PWM values formatted as left and right. The code parses the two values, clamps them to the valid range (−255 to 255), and then drives each motor using analogWrite on the H-bridge inputs (positive PWM drives forward, negative drives backward). A STOP command sets all motor PWM outputs to zero. On the Python side, I used helper functions to send DRIVE and STOP over BLE, and I ran timed motions by calling drive_for(left_pwm, right_pwm, duration_s) (for example, drive_for(80, 115, 2) to drive forward for 2 seconds with different left and right PWM values for calibration).
// Arduino snippet (BLE parsing + motor output)
case DRIVE: {
int leftPWM, rightPWM;
robot_cmd.get_next_value(leftPWM);
robot_cmd.get_next_value(rightPWM);
setBothPWM(leftPWM, rightPWM); // uses analogWrite to drive motors
txNotify("OK");
break;
}
case STOP:
stopBoth(); // sets all analogWrite outputs to 0
txNotify("STOPPED");
break;
# Python snippet (sending DRIVE/STOP)
def stop():
try:
ble.send_command(CMD.STOP, "")
except Exception as e:
print("STOP failed (disconnected):", e)
def drive(left_pwm: int, right_pwm: int):
left_pwm = max(-255, min(255, int(left_pwm)))
right_pwm = max(-255, min(255, int(right_pwm)))
ble.send_command(CMD.DRIVE, f"{left_pwm}|{right_pwm}")
def drive_for(left_pwm, right_pwm, duration_s):
drive(left_pwm, right_pwm)
time.sleep(duration_s)
stop()
After setting up BLE drive control, I tested the minimum PWM values required for the robot to move on the ground. I observed that the robot needs more power to start moving from rest due to static friction. The lowest values that consistently produced forward motion were approximately (15, 50) for the left and right motors. Below this range, one of the wheels would stall. I also tested higher values and found that the robot could still move straight around (200, 255). A stable mid-range value for straight driving was (80, 115).
From these tests, it was clear that the two motors do not spin at the same rate for the same PWM input. To compensate for this mismatch, I introduced a calibration factor based on the tested data. Using the mid-range straight-driving values (80, 115), the ratio between the motors is approximately 0.70. I then applied this scaling so that for a given base PWM value p, the left motor receives 0.70p while the right motor receives p. This allows the robot to maintain a fairly straight path across different speeds.
The video above shows the robot following a straight tape line for at least 2 meters. The robot started a little off centered following the line on the ground but still remains mostly straight at the end,
To demonstrate open-loop, I programmed the robot to execute a timed sequence of movements using BLE commands. The robot drove forward for 2 seconds and then performed an in-place turn for 1 second using preset PWM values. Since the motion was based only on fixed timing and motor commands without any feedback correction, this demonstrates open-loop control. A video below shows the robot completing the sequence successfully.
The video above shows the robot following a straight tape line for at least 2 meters. The robot started a little off centered following the line on the ground but still remains mostly straight at the end,
Using the oscilloscope measurement function, I estimated the PWM frequency generated by analogWrite() to be approximately 267 Hz. This frequency is sufficient for controlling our DC gear motors because the motor’s mechanical inertia smooths the voltage pulses and speed is primarily determined by duty cycle. Increasing the PWM frequency could reduce audible noise and torque ripple, but the default frequency was adequate for stable operation in this lab.
Through experimental testing, I determined that the robot requires a higher PWM value to start moving than to remain in motion due to static friction. After the robot was already moving, I gradually reduced the PWM and found that the lowest values that could sustain continuous forward motion were approximately 6 for the left motor and 20 for the right motor. Below these values, one or both wheels would stall.